using System;
using System.Configuration;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text;
using IDeal.Models;

namespace IDeal.Services
{
    public class CryptoServices : ICryptoServices
    {
        private readonly X509Certificate2 _certificate;
        private readonly SHA1Managed _sha1;

        public CryptoServices(ICertificateProvider certificateProvider)
        {
            _certificate = certificateProvider.GetCertificate();
            _sha1 = new SHA1Managed();
            _sha1.Initialize();
        }

        public Signature CreateSignature(string data)
        {
            var rsa = (RSACryptoServiceProvider)_certificate.PrivateKey;
            var hash = _sha1.ComputeHash(Encoding.ASCII.GetBytes(data));
            var signedHash = rsa.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));

            return new Signature
            {
                Fingerprint = _certificate.GetCertHashString(),
                Token = Convert.ToBase64String(signedHash)
            };
        }

        public void ValidateSignature(Signature signature, string data)
        {
            var key = GetPublicKeyByFingerprint(signature.Fingerprint);
            var hash = _sha1.ComputeHash(Encoding.ASCII.GetBytes(data));

            if(!key.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(signature.Token)))
                throw new SecurityException("Invalid signature received");
        }

        private static RSACryptoServiceProvider GetPublicKeyByFingerprint(string fingerprint)
        {
            RSACryptoServiceProvider key;
            WindowsImpersonationContext windowsImpersonationContext = null;
            try
            {
                var impersonationLevel = WindowsIdentity.GetCurrent().ImpersonationLevel;
                if (impersonationLevel == TokenImpersonationLevel.Delegation || impersonationLevel == TokenImpersonationLevel.Impersonation)
                {
                    windowsImpersonationContext = WindowsIdentity.Impersonate(IntPtr.Zero);
                }
                var x509Store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                x509Store.Open(OpenFlags.ReadOnly);

                var x509Certificate2Collection = x509Store.Certificates.Find(X509FindType.FindByThumbprint, fingerprint, false);
                if (x509Certificate2Collection.Count == 1)
                {
                    x509Store.Close();
                    key = (RSACryptoServiceProvider)x509Certificate2Collection[0].PublicKey.Key;
                }
                else
                {
                    var count = new object[2];
                    count[0] = x509Certificate2Collection.Count;
                    count[1] = fingerprint;
                    var str = string.Format("Found {0} certificates for fingerprint {1}, expected 1.", count);
                    throw new ConfigurationErrorsException(str);
                }
            }
            finally
            {
                if (windowsImpersonationContext != null)
                {
                    windowsImpersonationContext.Undo();
                }
            }
            return key;
        }
    }
}